﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;


public class DataMgr : MonoBehaviour
{
    //接收到的数据
    public string r_message;
    //udpClient 这个类是对udp进一层的封装. 这个类在构造的时候,就可以直接指定IP和端口,无需再调用Bind方法,去绑定.
    UdpClient localUDP;
    IPEndPoint serverPoint, localPoint;//服务端IP和端口、本地的IP和端口

    void Start()
    {

        //创建Socket实例
        //udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        //调用Dns.GetHostEntry的方法可以获得本机的IP
        //注意:客户端绑定的是本机IP,而服务端绑定的是公网IP
        //客户端的本地IP 会经过路由器 自动转化处理 所以客户端绑定的IP跟服务器实际打印出来的IP是不一样的.
        foreach (IPAddress _IPAddress in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
        {
            if (_IPAddress.AddressFamily.ToString() == "InterNetwork")
            {
                //绑定客户端的IP和端口,如果不绑定,路由不会自动处理内网IP和外网IP的转换,使得只能发送数据,无法接收数据.
                //udpClient.Bind(new IPEndPoint(_IPAddress, 7680));
                localPoint = new IPEndPoint(_IPAddress, 7680);
                localUDP = new UdpClient(localPoint);
            }
        }
        //这一个是服务器的IP和端口,通过IP找到远程主机,通过端口找到对应的服务端程序
        //serverPoint = new IPEndPoint(IPAddress.Parse("139.196.112.69"), 7679);
        serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7788);
        
        //接收数据
        ReceiveData();
    }
    #region 发送消息
    public void Send(string str)
    {
        new Thread(
            delegate ()
            {
                byte[] data = Encoding.UTF8.GetBytes(str);
                //BeginSend异步发送的功能,参数:发送的数据、数据的长度、服务端口、回调、UdpClient本地客户端
                localUDP.BeginSend(data, data.Length, serverPoint, SendCallback, localUDP);//调用BeginSend方法异步发送
                // udpClient.SendTo(data, serverPoint);//数据,发送的ip和端口号
            })
        { IsBackground = true }.Start(); //设置为后台线程:保证即使用户断开连接,数据依旧能够完整的发送完成,并启动线程
    }

    //IAsyncResult 它存储此异步操作的状态信息。
    private void SendCallback(IAsyncResult ar)
    {
        //IAsyncResult 异步结果
        UdpClient udpClient = (UdpClient)ar.AsyncState;
        //https://msdn.microsoft.com/zh-cn/library/ms145149.aspx
        //结束数据的异步发送
        udpClient.EndSend(ar);
    }
    #endregion

    #region 接收消息
    public void ReceiveData()
    {

        //开一个新线程接收UDP发送的数据
        new Thread(delegate ()  //delegate()也可写成 ()=>
        {
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 0);
            UDPState state = new UDPState(ipep, localUDP);//指定了接收数据的IP和端口,第二个参数指定了用于交互的Socet终端
            localUDP.BeginReceive(GetText, state); //异步接收
        })
        { IsBackground = true }.Start(); //设置为后台线程，并开启线程

    }

    private void GetText(IAsyncResult ar)
    {
        UDPState state = ar.AsyncState as UDPState;
        if (state != null)
        {
            IPEndPoint ipep = state.IPEP;//指发送数据的终端
            //这里接收到数据后，应该做数据完整检查，这里只是单纯做一个接收
            byte[] data = state.UDPClient.EndReceive(ar, ref ipep);
            r_message = Encoding.Default.GetString(data);
            //这里可以做一个数据检查 如：void CheckData(data)
            Debug.Log("接收到的数据" + r_message);
            //继续接收下一条消息
            state.UDPClient.BeginReceive(GetText, state);
        }
    }
    #endregion
    private void OnApplicationQuit()
    {
        //释放连接
        if (localUDP != null)
        {
            localUDP.Close();
        }

    }
}

/// <summary>
/// 自己创建的类UDPState 
/// </summary>
public class UDPState
{
    private UdpClient udpClient;

    public UdpClient UDPClient
    {
        get { return udpClient; }
    }

    private IPEndPoint ipep;
    public IPEndPoint IPEP
    {
        get { return ipep; }
    }

    //构造函数 传递IP和UDPClient两个对象,进行赋值
    public UDPState(IPEndPoint ipep, UdpClient udpClient)
    {
        this.ipep = ipep;
        this.udpClient = udpClient;
    }
}